/**
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-09-C-0002, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * (c) 2009 The MITRE Corporation. All Rights Reserved.
 *
 * SPMIF to OPNET Translator
 * Author: ewalters
 * Created: 3:26:21 PM, Aug 14, 2007
 * MITRE Corporation
 */
package com.mitre.spmif2opnet.generation;

import java.util.LinkedList;

import com.mitre.spmif2opnet.VisitorContext;
import com.mitre.spmif2opnet.analysis.AnalysisException;
import com.mitre.spmif2opnet.analysis.ast.CompoundNode;
import com.mitre.spmif2opnet.analysis.ast.Device;
import com.mitre.spmif2opnet.analysis.ast.ExpandedNode;
import com.mitre.spmif2opnet.analysis.ast.Graph;
import com.mitre.spmif2opnet.analysis.ast.LinkNode;
import com.mitre.spmif2opnet.analysis.ast.Node;
import com.mitre.spmif2opnet.analysis.ast.NodeVisitor;
import com.mitre.spmif2opnet.analysis.ast.RepNode;
import com.mitre.spmif2opnet.analysis.ast.SynchroNode;
import com.mitre.spmif2opnet.generation.ir.Endpoint;
import com.mitre.spmif2opnet.generation.ir.Job;
import com.mitre.spmif2opnet.generation.ir.Phase;

/**
 * Implements visitor pattern over S-PMIF Nodes to generate the 
 *  corresponding OPNET structures.
 * NOTE: This code is also greatly in flux - bear with it!
 */
public class OpnetNodeVisitor implements NodeVisitor<GenerationContext> {
	
	/**
	 * Empty constructor
	 */
	public OpnetNodeVisitor() {}

	/** 
	 * For basic nodes, we construct a processing phase with no destination
	 * Parameter node "n" better be the same node as vc.getCurrNode()  
	 */
	public VisitorContext<GenerationContext> visit(Node n, VisitorContext<GenerationContext> vc) {
		GenerationContext c = vc.getContext();

		System.out.println("[INFO] Processing Simple Node: " + n.getId());

		try {
			// Step #1 - create the endpoint
			String endpoint_name = c.getCurrResource();

			// See "Endpoint.SpecialPoint" - we may set other flags in post-processing
			Endpoint source = new Endpoint(endpoint_name);
			source.setPointDesignation(Endpoint.SpecialPoint.NAMED);
//			source.setPacketSize(1024);      // NOTE: how to get this into S-PMIF?
//			source.setInterrequestTime(0.1); // NOTE: how do we also get this?
			
			double service_time = c.getTiming(Device.DeviceCategory.DEVICE_PROCESSING);
			source.setInitTime(service_time);
			
			// Step #2 - Create a new job
			int priority = c.getPriority();
			String device_name = c.getDeviceId(endpoint_name, Device.DeviceCategory.DEVICE_PROCESSING);
			String long_model_name = c.getLongModelName(endpoint_name, Device.DeviceCategory.DEVICE_PROCESSING);
			String job_class = c.getJobClass(endpoint_name, Device.DeviceCategory.DEVICE_PROCESSING);
			Job job = new Job(n.getName() + "_" + Job.getNewJobName(), device_name, priority, service_time, long_model_name, job_class);
			
			source.setJobName(job.getName());
			
			// Step #3 - Create a loop phase
			Phase p = new Phase(n.getId(), c.getPrevPhase());
			// NOTE: Phase defaults assumed:
			//  Prev Phase Ends/Application starts
			p.setName(n.getName());
			if (!c.isFirstTaskDone()) {
				p.setFirstPhase(true);
				c.firstTaskDone();
			}
			
			// Store everything
			p.setSource(source);
			c.addJob(job);
			c.addPhase(p);
			// NOTE: Since setPrevPhase automatically populates the previous endpoint,
			//  we use this to set the resource utilized by the node, which is 
			//  important when we generate the node.  In other words, regardless of
			//  the phase, we need the endpoint set for basic nodes
			// However, this has the side-effect of making the phase visible, which
			//  means that we get the name of the immediatly preceeding phase as opposed to
			//  "previous phase ends" in the description.  Take that for what you will.
			c.setPrevPhase(p);
		} catch (Exception exc) {
			System.out.println(exc.getMessage());
			System.exit(-1);
		}
		
		vc.setContext(c);
		return vc;
	}

	/** 
	 * Link nodes are straightforward - all we do is pull in the linked node, and follow it!
	 * Note that the "current node" does not change - the successor is derived from the 
	 *  linking node - not the linked >to< node
	 */
	public VisitorContext<GenerationContext> visit(LinkNode n, VisitorContext<GenerationContext> c) {
		Node link_node = n.getLinkedToNode().copy();
		NodeVisitor<GenerationContext> new_visitor = new OpnetNodeVisitor();
		VisitorContext<GenerationContext> vc 
			= link_node.accept(new_visitor, new VisitorContext<GenerationContext>(c.getContext()));
		return vc;
	}

	/**
	 * Visit the expanded node, i.e. the graph inside
	 */
	public VisitorContext<GenerationContext> visit(ExpandedNode n, VisitorContext<GenerationContext> c) {
		Graph exp_graph = n.getGraph().copy();

		System.out.println("[INFO] Processing Expanded Node: " + n.getId());
		GenerationContext gc_old = c.getContext();
		GenerationContext gc_new = new GenerationContext(new OpnetGraphConverter(),
				gc_old.getPriorities());
		
		Phase prev_phase = gc_old.getPrevPhase();
		if (prev_phase == null && !gc_old.getPhases().isEmpty()) {
			prev_phase = gc_old.getPhases().get(gc_old.getPhases().size() - 1);
			gc_old.setPrevPhase(prev_phase);
		}
		System.out.println("[INFO] Expanded Node details for " + n.getId() 
				+ ": previous phase = " + (prev_phase == null ? "NONE" : prev_phase.toString()) 		
				+ ", endpoint = " + (gc_old.getPrevResource() == null ? "NONE" : gc_old.getPrevResource().getName()));		
		
		// For each graph, begin with the start node
		LinkedList<Node> path = new LinkedList<Node>();
		path.add(exp_graph.getStartNode());
		
		// Initialize the finite state machine
		ProcNetworkFSM fsm = new ProcNetworkFSM();
		
		// Initalize info with prev context
		gc_new.setCurrGraphId(exp_graph.getIdentifier());
		gc_new.transferControlInfo(gc_old);

		try {
			while (!path.isEmpty()) {
				// Put the current node in the context
				gc_new.setCurrNode(path.peek());
				// Get the name of our resource, and thus our logical node
				String res = gc_new.getResourceId(Device.DeviceCategory.DEVICE_PROCESSING);
				boolean diff_resource = gc_new.endpointChanged(res);
				ProcNetworkFSM.Letter l =
					ProcNetworkFSM.parseLetter(gc_new.getCurrNode(), 
							gc_new.getCurrProject().getOverhead(), diff_resource);
				// Let the FSM chew on it
				fsm.process(l, gc_new);
				// Add successors to the worklist
				path.addAll(exp_graph.getSuccessors(gc_new.getCurrNode()));
				// Remove the node from the work queue
				path.remove();
			}
		} catch (GenerationException exc) {
			System.out.println(exc);
			System.exit(-1);
		}

		// Transfer info
		gc_old.transferNetwork(gc_new);
		
		// Grab changed info and replace in old context
		gc_old.transferControlInfo(gc_new);
		c.setContext(gc_old);
		return c;
	}

	/**
	 * NOTE: Synchro nodes have fallen out of favor in the current implementation.
	 * This code may be moribund - you have been warned!
	 */
	public VisitorContext<GenerationContext> visit(SynchroNode n, VisitorContext<GenerationContext> c) {

		System.out.println("[INFO] Processing Synchronization Node: " + n.getId());
		Graph sync_graph = null;
		Phase prev_phase = null;
		try {
			sync_graph = n.getPartner().getStartGraph();
		} catch (AnalysisException exc) {
			System.out.println(exc);
			System.exit(-1);
		}

		GenerationContext gc_old = c.getContext();
		// Store the last phase
		prev_phase = gc_old.getPhases().get(gc_old.getPhases().size() - 1);
		GenerationContext gc_new = new GenerationContext(new OpnetGraphConverter(),
				gc_old.getPriorities());

		// For each graph, begin with the start node
		LinkedList<Node> path = new LinkedList<Node>();
		path.add(sync_graph.getStartNode());
		
		// Initialize the finite state machine
		ProcNetworkFSM fsm = new ProcNetworkFSM();
		
		// Initalize info with prev context
		gc_new.setCurrGraphId(sync_graph.getIdentifier());
		gc_new.transferControlInfo(gc_old);
		
		try {
			while (!path.isEmpty()) {
				// Put the current node in the context
				gc_new.setCurrNode(path.peek());
				// Get the name of our resource, and thus our logical node
				String res = gc_new.getResourceId(Device.DeviceCategory.DEVICE_PROCESSING);
				boolean diff_resource = gc_new.endpointChanged(res);
				ProcNetworkFSM.Letter l =
					ProcNetworkFSM.parseLetter(gc_new.getCurrNode(), 
							gc_new.getCurrProject().getOverhead(), diff_resource);
				// Let the FSM chew on it
				fsm.process(l, gc_new);
				// Add successors to the worklist
				path.addAll(sync_graph.getSuccessors(gc_new.getCurrNode()));
				// Remove the node from the work queue
				path.remove();
			}
		} catch (GenerationException exc) {
			System.out.println(exc);
			System.exit(-1);
		}

		// Transfer info
		gc_old.transferNetwork(gc_new);
		
		// Grab changed info and replace in old context
		Phase out_phase = n.isAsynchronous() ? prev_phase : gc_old.getPrevPhase();
		gc_old.transferControlInfo(gc_new, out_phase);
		c.setContext(gc_old);
		return c;
	}

	/**
	 * Visit a compound node - not that different
	 */
	public VisitorContext<GenerationContext> visit(CompoundNode n, VisitorContext<GenerationContext> vc) {
		System.out.println("[INFO] Processing Compound Split Node " + n.getType() + ": " + n.getId());

		if (!n.getType().equals(CompoundNode.CompoundType.COMPOUND_SPLIT)) {
			System.out.println("Processing invalid compound graph type " + n.getType() + ": " + n.getId());
			System.exit(-1);
		}

		GenerationContext gc_old = vc.getContext();
		Phase prev_phase = gc_old.getPrevPhase();
		if (prev_phase == null && !gc_old.getPhases().isEmpty()) {
			prev_phase = gc_old.getPhases().get(gc_old.getPhases().size() - 1);
			gc_old.setPrevPhase(prev_phase);
		}
		System.out.println("[INFO] Split Node details for " + n.getId() 
				+ ": previous phase = " + (prev_phase == null ? "NONE" : prev_phase.toString()) 		
				+ ", endpoint = " + (gc_old.getPrevResource() == null ? "NONE" : gc_old.getPrevResource().getName()));		

	
		// Initialize the finite state machine
		ProcNetworkFSM fsm = null;
		if (gc_old.hasCurrState()) {
			fsm = new ProcNetworkFSM(gc_old.getCurrState());
		} else {
			fsm = new ProcNetworkFSM();
		}

		for (Node child_n : n.getChildren()) {
			GenerationContext gc_new = new GenerationContext(new OpnetGraphConverter(),
					gc_old.getPriorities());

			// Initalize info with prev context
			gc_new.transferControlInfo(gc_old, prev_phase);
			
			try {
				// Put the current node in the context
				gc_new.setCurrNode(child_n);
				// Get the name of our resource, and thus our logical node
				String res = gc_new.getResourceId(Device.DeviceCategory.DEVICE_PROCESSING);
				boolean diff_resource = gc_new.endpointChanged(res);
				ProcNetworkFSM.Letter l =
					ProcNetworkFSM.parseLetter(gc_new.getCurrNode(), 
							gc_new.getCurrProject().getOverhead(), diff_resource);
				// Let the FSM chew on it
				fsm.process(l, gc_new);
			} catch (GenerationException exc) {
				System.out.println(exc);
				System.exit(-1);
			}

			// Transfer info
			gc_old.transferNetwork(gc_new);
		}

		vc.setContext(gc_old);
		return vc;
	}

	/**
	 * Generate for a repeating node
	 */
	public VisitorContext<GenerationContext> visit(RepNode n, VisitorContext<GenerationContext> vc) {
		Node child_n = n.getChild();
		NodeVisitor<GenerationContext> new_visitor = new OpnetNodeVisitor();
		for (int i = 0; i < n.getFactor(); i++) {
			vc = child_n.copy().accept(new_visitor, vc);
		}
		
		return vc;
	}

}
